﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Scene;
using RayEngine.Spectral.Entities;
using RayEngine.Spectral.Entities.Brdfs;
using RayEngine.Spectral.Entities.Lighting;
using RayEngine.Spectral.Entities.Materials;
using RayEngine.Spectral.Entities.Textures;
using SolidObject = RayEngine.Spectral.Entities.SolidObject;

namespace RayEngine.Spectral.Engine
{
    public class SceneManager
    {
        private string DefaultLightProfile = "D6500";

        private RayEngineScene Scene;
        private readonly Dictionary<string, SolidMaterial> solidMaterials;
        private readonly Dictionary<string, LightMaterial> lightMaterials;


        private readonly LightsourceObject[] Lights;
        private readonly SolidObject[] Objects;

        public SceneManager(RayEngineScene scn)
        {
            this.Scene = scn;
            this.solidMaterials = new Dictionary<string, SolidMaterial>();
            this.lightMaterials = new Dictionary<string, LightMaterial>();
            this.Objects = new SolidObject[scn.Meshes.Length];
            this.Lights = new LightsourceObject[scn.Lights.Length];
            if (EngineConfiguration.Instance.SceneManager == null)
            {
                EngineConfiguration.Instance.SceneManager = this;
            }
        }

        public SolidObject GetObject(uint index)
        {
// ReSharper disable once LoopCanBeConvertedToQuery
            for (var i = 0; i < Objects.Length; i++)
            {
                if (Objects[i].OwnsIndex(index))
                    return Objects[i];
            }
            return null;
        }

        public SolidObject GetSolidById(int id)
        {
            return Objects[id];
        }

        public LightsourceObject GetLightById(int id)
        {
            return Lights[id];
        }

        public void Build()
        {
            int id = 0;
            Tracer.TraceLine("Spectral-#-  Start scene building-");
            Tracer.TraceLine("Spectral-##- Materials-");

            foreach (var material in Scene.MaterialProvider.GetAll())
            {
                if (!solidMaterials.ContainsKey(material.MaterialName.ToLower()))
                {
                    var mat = new SolidMaterial(id++, material, Scene, BrdfFactory.Instance.CreateBrdf(material));
                    solidMaterials.Add(material.MaterialName.ToLower(), mat);
                }
            }

            lightMaterials.Add("D6500", new LightMaterial(SPD_Data.FromSampled(SPD_Data.D6500, SpectrumType.Illuminant)));
            lightMaterials.Add("D5000", new LightMaterial(SPD_Data.FromSampled(SPD_Data.D5000, SpectrumType.Illuminant)));
            lightMaterials.Add("A", new LightMaterial(SPD_Data.FromSampled(SPD_Data.A, SpectrumType.Illuminant)));

            Tracer.TraceLine("Spectral-##- Solids-");


            int objects = 0, lights = 0;
            foreach (var triangleMesh in Scene.Meshes.Cast<TriangleMeshInfo>())
            {
                var solid = new SolidObject(solidMaterials[triangleMesh.MaterialName.ToLower()], triangleMesh)
                {
                    IsLightsource = IsLight(triangleMesh.MeshName),
                    ID = objects+1
                };
                this.Objects[objects++] = solid;
            }

            Tracer.TraceLine("Spectral-##- Lights-");

            foreach (var lt in Scene.Lights)
            {
                BaseLight light = null;
                int solidId = 0;
                LightMaterial lightMat = lightMaterials.ContainsKey(lt.Profile.Name.ToUpper())
                    ? lightMaterials[lt.Profile.Name.ToUpper()]
                    : lightMaterials[DefaultLightProfile];

                if (lt is RayDen.RayEngine.Data.PointLight)
                {
                    var l = lt as RayDen.RayEngine.Data.PointLight;
                    light = new RayEngine.Spectral.Entities.Lighting.PointLight(lightMat, l.Position);
                }
                else if (lt is InfiniteLight)
                {
                    var l = lt as InfiniteLight;
                    light = new BasicInfiniteLight(l.Sampler, lightMat);
                }
                else if (lt is RayDen.RayEngine.Data.MeshLight)
                {
                    var l = lt as RayDen.RayEngine.Data.MeshLight;
                    light = new TriangleMeshLight(lightMat, Scene, (TriangleMeshInfo)Scene.Meshes.First(mesh => mesh.MeshName.ToLower().Equals(l.MeshName.ToLower())));
                    var prim =
                        this.Objects.FirstOrDefault(
                            mesh => mesh.Mesh.MeshName.Equals(l.MeshName, StringComparison.InvariantCultureIgnoreCase));
                    if (prim != null)
                    {
                        prim.ChildID = lights;
                        solidId = Array.IndexOf(this.Objects, prim);
                    }
                }

                if (light != null)
                    this.Lights[lights++] = new LightsourceObject(lightMat, light)
                    {
                        ID = lights, SolidId = solidId
                    };

            }
            Tracer.TraceLine("Spectral-#-  Complete scene building-");

        }

        private bool IsLight(string name)
        {
            if (name.Contains("lt"))
                return true;
            return false;
        }
    }
}
